package cn.hy.completable.案例.下单过程;

import cn.hy.completable.实践.LogErrorAction;
import cn.hy.completable.案例.下单过程.bean.Product;
import org.junit.Assert;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @author zhaomu
 * <p>
 * 一个顾客下单的流程模拟:
 * 1、获取基本产品信息（此处查询数据库）
 * 2、获取每一个产品的价格（假设此处需要通过第三方服务平台进行实时定价，产品不同调用的平台亦不同，所有此处是挨个获取）
 * 3、计算产品总价
 * 4、获取用户余额（此处也是调取第三方服务，获取用户账户余额）
 * 5、比对余额是否充足
 * 6、如果余额充足则提示购买成功，并扣去花费，更新用户余额（此处也是调取第三方服务，更新用户账户余额）
 * 7、返回是否购买成功
 */
public class App {


    public static void main(String[] args) throws Exception {
        App app = new App();
        // 1. 非并发查询
        app.searchProduct();
        // 2. 线程池优化
        app.searchProductConcurrent();
        // 3. CompletableFuture 优化
        app.searchProductByCompletableFuture();
    }


    public void searchProductByCompletableFuture() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ExecutorService subExecutorService = Executors.newFixedThreadPool(5);
        try {
            long timeStart = System.currentTimeMillis();

            CompletableFuture<List<Product>> listCompletableFuture = CompletableFuture.supplyAsync(() -> {
                        try {
                            // 1.获取基本产品信息
                            return getProducts();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        return null;
                    }, executorService)
                    .whenComplete(new LogErrorAction<>("getProducts"))
                    .thenApply(productList -> {
                        // 2.并发获取每一个产品的价格
                        productList
                                .stream()
                                .forEach(product -> {
                                            BigDecimal realPrice = CompletableFuture
                                                    .supplyAsync(() -> getProductPrice(product), subExecutorService)
                                                    .join();
                                            product.setPrice(realPrice);
                                        }
                                );
                        return productList;
                    });

            //3.获取用户余额
            CompletableFuture<BigDecimal> userAccountCf = CompletableFuture.supplyAsync(() -> {
                try {
                    return getUserAccount();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }, executorService).whenComplete(new LogErrorAction<>("getUserAccount"));

            // List<Product> allProduct = listCompletableFuture.join();
            // 同步阻塞获取所有数据结果
            CompletableFuture<Void> cf3 = CompletableFuture.allOf(userAccountCf, listCompletableFuture);
            cf3.thenAccept(v -> {
                //这里的join并不会阻塞，因为传给thenAccept的函数是在 cf 全部完成时，才会执行。
                List<Product> allProduct = listCompletableFuture.join();
                // 4、计算产品总价
                BigDecimal priceSum = allProduct.stream()
                        .map(Product::getPrice)
                        .reduce(new BigDecimal(0), BigDecimal::add);
                System.out.println("searchProductByCompletableFuture:产品总价：" + priceSum);
                Assert.assertEquals("2780", priceSum.toString());

                BigDecimal userAccount = userAccountCf.join();
                // 5、比对余额是否充足
                String result = "";
                if (userAccount.compareTo(priceSum) < 0) {
                    result = "余额不足，购买失败";
                } else {
                    result = "余额充足，购买成功";
                    // 6、更新用户的余额
                    BigDecimal left = userAccount.subtract(priceSum);
                    System.out.println("searchProductByCompletableFuture:用户购买后余额：" + left);
                    updateUserAccount(left);
                }
                System.out.println(result);
            });
            long timeUseMs = System.currentTimeMillis() - timeStart;
            System.out.println("searchProductByCompletableFuture:并发查询耗时：" + timeUseMs);
        } finally {
            executorService.shutdown();
            subExecutorService.shutdown();
        }
    }


    public void searchProductConcurrent() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        try {
            long timeStart = System.currentTimeMillis();
            // 1、获取基本产品信息
            Future<List<Product>> productListFuture = ConcurrentUtil.doJob(executorService, this::getProducts);
            // 2、获取用户余额
            Future<BigDecimal> userAccountFuture = ConcurrentUtil.doJob(executorService, this::getUserAccount);
            // 1、2并发的查询结果
            List<Product> productList = ConcurrentUtil.futureGet(productListFuture);
            BigDecimal userAccount = ConcurrentUtil.futureGet(userAccountFuture);
            // 3、并发获取每一个产品的价格
            List<Future<BigDecimal>> priceFutureList = new ArrayList<>();
            for (Product product : productList) {
                priceFutureList.add(ConcurrentUtil.doJob(executorService, () -> getProductPrice(product)));
            }
            for (int i = 0; i < productList.size(); i++) {
                productList.get(i).setPrice(ConcurrentUtil.futureGet(priceFutureList.get(i)));
            }
            // 3、计算产品总价
            BigDecimal priceSum = productList.stream()
                    .map(Product::getPrice)
                    .reduce(new BigDecimal(0), BigDecimal::add);
            System.out.println("searchProductConcurrent:产品总价：" + priceSum);
            Assert.assertEquals("2780", priceSum.toString());
            // 5、比对余额是否充足
            String result;
            if (userAccount.compareTo(priceSum) < 0) {
                result = "余额不足，购买失败";
            } else {
                result = "余额充足，购买成功";
                // 6、更新用户的余额
                BigDecimal left = userAccount.subtract(priceSum);
                System.out.println("searchProductConcurrent:用户购买后余额：" + left);
                updateUserAccount(left);
            }
            System.out.println(result);
            long timeUseMs = System.currentTimeMillis() - timeStart;
            System.out.println("searchProductConcurrent:并发查询耗时：" + timeUseMs);
        } finally {
            executorService.shutdown();
        }
    }


    public void searchProduct() throws InterruptedException {
        long timeStart = System.currentTimeMillis();
        // 1、获取基本产品信息
        List<Product> productList = getProducts();
        // 2、获取每一个产品的价格
        for (Product product : productList) {
            product.setPrice(getProductPrice(product));
        }
        // 3、计算产品总价
        BigDecimal priceSum = productList.stream()
                .map(Product::getPrice)
                .reduce(new BigDecimal(0), BigDecimal::add);
        System.out.println("searchProduct: 产品总价：" + priceSum);
        Assert.assertEquals("2780", priceSum.toString());
        // 4、获取用户余额
        BigDecimal userAccount = getUserAccount();
        // 5、比对余额是否充足
        String result;
        if (userAccount.compareTo(priceSum) < 0) {
            result = "余额不足，购买失败";
        } else {
            result = "余额充足，购买成功";
            // 6、更新用户的余额
            BigDecimal left = userAccount.subtract(priceSum);
            System.out.println("searchProduct:  用户购买后余额：" + left);
            updateUserAccount(left);
        }
        System.out.println(result);
        long timeUseMs = System.currentTimeMillis() - timeStart;
        System.out.println("searchProduct: 非并发查询耗时：" + timeUseMs);
    }


    /**
     * 获取产品价格，模拟调用外部服务
     *
     * @param product 产品
     * @return 产品价格
     * @throws InterruptedException Exception
     */
    public BigDecimal getProductPrice(Product product) {
        // 模拟耗时查询——调用外部服务接口
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new BigDecimal(product.getId() * 10 + 233);
    }

    /**
     * 获取产品列表，模拟从数据库查询数据
     *
     * @return 产品列表
     * @throws InterruptedException Exception
     */
    public List<Product> getProducts() throws InterruptedException {
        List<Product> productList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Product product = new Product();
            product.setId((long) i);
            product.setName("product" + i);
            product.setCreateTime(new Date());
            productList.add(product);
        }
        // 模拟耗时查询
        Thread.sleep(1000);
        return productList;
    }

    /**
     * 获取用户的账户余额
     *
     * @return 账户余额
     * @throws InterruptedException Exception
     */
    public BigDecimal getUserAccount() throws InterruptedException {
        // 模拟耗时查询 —— 调用外部服务获取当前用户余额
        Thread.sleep(500);
        return new BigDecimal(3000);
    }

    /**
     * 更新用户的账户余额
     *
     * @param left 账户余额
     * @throws InterruptedException Exception
     */
    public boolean updateUserAccount(BigDecimal left) {
        // 模拟耗时操作 —— 调用外部服务更新当前用户余额
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

}
